package com.blustreaksw.cyclecoach.export;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

import com.blustreaksw.cyclecoach.data.WorkoutDatabaseHelper;
import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.DropboxAPI.DropboxFileInfo;
import com.dropbox.client2.android.AndroidAuthSession;
import com.dropbox.client2.android.AuthActivity;
import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.session.AccessTokenPair;
import com.dropbox.client2.session.AppKeyPair;
import com.dropbox.client2.session.Session.AccessType;
import com.dropbox.client2.session.TokenPair;

public class DropboxConnector {
	
	private static final String TAG = "DropboxExport";

    final static private String APP_KEY = "244l35t1bbil71s";
    final static private String APP_SECRET = "nd95j9jp3j3bzcm";
    final static private AccessType ACCESS_TYPE = AccessType.APP_FOLDER;

    // Shared preferences info
    final static private String ACCOUNT_PREFS_NAME = "CycleCoach_DropboxPrefs";
    final static private String ACCESS_KEY_NAME = "ACCESS_KEY";
    final static private String ACCESS_SECRET_NAME = "ACCESS_SECRET";
    final static private String FILE_STORAGE_DIRECTORY = "/data/data/com.blustreaksw.cyclecoach/files/";


    DropboxAPI<AndroidAuthSession> mApi;

    private boolean mLoggedIn;
    private boolean mAuthenticationRequested;
    
    private static DropboxConnector sDropboxConnector;
    
    private ProgressDialog mProgressDialog;
    private ExportSessionDataToDropboxTask mExportSessionDataToDropboxTask;
    private Context mContext;
    
    private DropboxConnector(Context c) {
    	
    	AndroidAuthSession session = buildSession(c);
        mApi = new DropboxAPI<AndroidAuthSession>(session);
    	mAuthenticationRequested = false;
    }
    
    
    /**
     * Accessor function for static singleton instance of
     * DropboxConnector object.
     * 
     * @param c
     * 	Context reference to use (Activity)
     * 
     * @return
     * 	Singleton instance of DropboxConnector
     */
    public static DropboxConnector getInstance(Context c) {
    	if(sDropboxConnector == null) {
    		sDropboxConnector = new DropboxConnector(c);
    		Log.d(TAG, "Created new DropboxConnector instance.");
    	}
    	
    	Log.d(TAG, "Returning DropboxConnector instance.");
    	return sDropboxConnector;
    }


    /** 
     * Intended for use by the configuration activity when we are flagged as 
     * logged out of Dropbox and the user wants to link with their account.
     * 
     * @param c
     * 	Context reference to use (Activity)
     */
    public void connectToDropbox(Context c) {
    	
    	mAuthenticationRequested = true;
        // Start the remote authentication
        mApi.getSession().startAuthentication(c);
    }
    
    public boolean authenticationPending() { return mAuthenticationRequested; }
    
    public void completeAuthentication(Context c) {
    	
    	if(mAuthenticationRequested) {
    	
	    	AndroidAuthSession session = mApi.getSession();
	    	
	    	if (session.authenticationSuccessful()) {
	            try {
	                // Mandatory call to complete the auth
	                session.finishAuthentication();
	
	                // Store it locally in our app for later use
	                TokenPair tokens = session.getAccessTokenPair();
	                storeKeys(tokens.key, tokens.secret, c);
	                Log.d(TAG, "Stored dropbox keys: " + tokens.key + ", " + tokens.secret);
	                setLoggedIn(true);
	            } catch (IllegalStateException e) {
	            	Toast.makeText(c, "Couldn't authenticate with Dropbox:" + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
	                Log.i(TAG, "Error authenticating", e);
	            }
	        }
	    	
	    	mAuthenticationRequested = false;
    	}
    }

    /**
     * Clear out persisted Dropbox connection by removing App key and
     * secret key.
     * 
     * @param c
     * 	Context reference to use (Activity)
     */
    public void logOut(Context c) {
        // Remove credentials from the session
        mApi.getSession().unlink();

        // Clear our stored keys
        clearKeys(c);
        // Change UI state to display logged out version
        setLoggedIn(false);
    }

    /*
     * Toggle whether we currently have a set of keys that we can
     * use to access the user's Dropbox account.
     */
    private void setLoggedIn(boolean loggedIn) {
    	mLoggedIn = loggedIn;
    }
    
    /**
     * Find out whether we are currently authenticated with the
     * user's Dropbox account.
     * 
     * @return
     * 	boolean: TRUE if logged in, FALSE otherwise
     */
    public boolean isLoggedIn() { return mLoggedIn; }
    

    /**
     * Shows keeping the access keys returned from Trusted Authenticator in a local
     * store, rather than storing user name & password, and re-authenticating each
     * time (which is not to be done, ever).
     *
     * @return Array of [access_key, access_secret], or null if none stored
     */
    public String[] getKeys(Context c) {
        SharedPreferences prefs = c.getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
        String key = prefs.getString(ACCESS_KEY_NAME, null);
        String secret = prefs.getString(ACCESS_SECRET_NAME, null);
        if (key != null && secret != null) {
        	String[] ret = new String[2];
        	ret[0] = key;
        	ret[1] = secret;
        	Log.d(TAG, "Found stored keys.");
        	return ret;
        } else {
        	Log.d(TAG, "No keys found.");
        	return null;
        }
    }

    /*
     * Shows keeping the access keys returned from Trusted Authenticator in a local
     * store, rather than storing user name & password, and re-authenticating each
     * time (which is not to be done, ever).
     */
    private void storeKeys(String key, String secret, Context c) {
        // Save the access key for later
        SharedPreferences prefs = c.getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
        Editor edit = prefs.edit();
        edit.putString(ACCESS_KEY_NAME, key);
        edit.putString(ACCESS_SECRET_NAME, secret);
        edit.commit();
    }

    /**
     * Delete the access keys that we stored previously.
     */
    public void clearKeys(Context c) {
        SharedPreferences prefs = c.getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
        Editor edit = prefs.edit();
        edit.clear();
        edit.commit();
    }

    /*
     * Checks if we have a set of access keys to use to connect to Dropbox,
     * and initiates a new connection if we need to reauthenticate.
     */
    private AndroidAuthSession buildSession(Context c) {
        AppKeyPair appKeyPair = new AppKeyPair(APP_KEY, APP_SECRET);
        AndroidAuthSession session;

        String[] stored = getKeys(c);
        if (stored != null) {
            AccessTokenPair accessToken = new AccessTokenPair(stored[0], stored[1]);
            session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE, accessToken);
            setLoggedIn(true);
        } else {
            session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE);
        }

        return session;
    }
    
    private class ExportSessionDataToDropboxTask extends AsyncTask<Integer, Integer, Long> {
    	
		@Override
		protected Long doInBackground(Integer... params) {
			
			long startTime = Calendar.getInstance().getTimeInMillis();
			WorkoutDatabaseHelper workoutDb = WorkoutDatabaseHelper.getInstance();
	    	String fileNameHeader = new String("CycleCoach_");
	    	int sessionId = params[0];
	    	String startTimeDateFormatted = workoutDb.getStartTimeDateForSession(sessionId);
	    	fileNameHeader += startTimeDateFormatted;
	    	int progress = 0;
	    
	    	String heartRateFile = workoutDb.exportTableDataForSessionId(sessionId, fileNameHeader, WorkoutDatabaseHelper.TABLE_HR, mContext);
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	if(heartRateFile != null) {
	    		sendFile(heartRateFile, startTimeDateFormatted);
	    	}
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	String locationFile = workoutDb.exportTableDataForSessionId(sessionId, fileNameHeader, WorkoutDatabaseHelper.TABLE_LOC, mContext);
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	if(locationFile != null) {
	    		sendFile(locationFile, startTimeDateFormatted);
	    	}
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	String cscFile = workoutDb.exportTableDataForSessionId(sessionId, fileNameHeader, WorkoutDatabaseHelper.TABLE_CSC, mContext);
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	if(cscFile != null) {
	    		sendFile(cscFile, startTimeDateFormatted);
	    	}
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	String powerFile = workoutDb.exportTableDataForSessionId(sessionId, fileNameHeader, WorkoutDatabaseHelper.TABLE_POW, mContext);
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	if(powerFile != null) {
	    		sendFile(powerFile, startTimeDateFormatted);
	    	}
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	String summaryFile = workoutDb.exportTableDataForSessionId(sessionId, fileNameHeader, WorkoutDatabaseHelper.TABLE_WS, mContext);
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	if(summaryFile != null) {
	    		sendFile(summaryFile, startTimeDateFormatted);
	    	}
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	String hpzFile = workoutDb.exportTableDataForSessionId(sessionId, fileNameHeader, WorkoutDatabaseHelper.TABLE_HPZ, mContext);
	    	
	    	publishProgress(++progress);
	    	if(isCancelled()) return (Calendar.getInstance().getTimeInMillis() - startTime);
	    	
	    	if(hpzFile != null) {
	    		sendFile(hpzFile, startTimeDateFormatted);
	    	}
	    	
	    	publishProgress(++progress);
	    	
			return (Calendar.getInstance().getTimeInMillis() - startTime);
		}
		
		@Override
		protected void onProgressUpdate(Integer... progress) {
			mProgressDialog.incrementProgressBy(1);
		}
		
		@Override
		protected void onPostExecute(Long timeToComplete) {
			Log.d(TAG, "Finished session export in " + timeToComplete + " seconds.");
			mProgressDialog.dismiss();
		}
    	
    }
    
    public void exportSessionDataToDropbox(int sessionId, Context c) {
    	mContext = c;
    	mProgressDialog = new ProgressDialog(c);
    	mProgressDialog.setTitle("Dropbox");
    	mProgressDialog.setMessage("Exporting workout data...");
    	mProgressDialog.setProgressStyle(mProgressDialog.STYLE_HORIZONTAL);
    	mProgressDialog.setProgress(0);
    	mProgressDialog.setMax(12);
    	mProgressDialog.setCancelable(true);
    	mProgressDialog.setOnDismissListener(new OnDismissListener() {

			@Override
			public void onDismiss(DialogInterface dialog) {
				mExportSessionDataToDropboxTask.cancel(true);
				
			}
    		
    	});
    	mProgressDialog.show();
    	mExportSessionDataToDropboxTask = new ExportSessionDataToDropboxTask();
    	mExportSessionDataToDropboxTask.execute(sessionId);
    }
    
    /**
     * Upload a file to Dropbox.
     * @param fileName
     * @return
     * 	boolean: TRUE on successful upload, FALSE otherwise.
     */
    private boolean sendFile(String fileName, String dropboxDirectory) {
    	
    	if(isLoggedIn() == false) return false;
    	
		try {
			File file = new File("/data/data/" + mContext.getPackageName() + "/files/" + fileName);
			FileInputStream inputStream = new FileInputStream(file);
			com.dropbox.client2.DropboxAPI.Entry response = 
					mApi.putFileOverwrite("/" + dropboxDirectory + "/" + fileName, inputStream, file.length(), null);
			Log.d("DbExampleLog", "The uploaded file's rev is: " + response.rev);
			inputStream.close();
			file.delete();
			
		} catch (FileNotFoundException e) {
			Log.d(TAG, "Upload failed, " + e.toString());
			return false;
		} catch(DropboxException e) {
			Log.d(TAG, "Upload failed, " + e.toString());
			return false;
		} catch(IOException e) {
			Log.d(TAG, "Upload failed, " + e.toString());
			return false;
		}
		
		return true;
	}
	
    /**
     * Download a file from Dropbox.
     * @param fileName
     * @return
     * 	boolean: TRUE on successful download, FALSE otherwise.
     */
	private boolean getFile(String fileName) {
		
		if(isLoggedIn() == false) return false;
		
		try {
			File file = new File(fileName);
			FileOutputStream outputStream;
			outputStream = new FileOutputStream(file);
			DropboxFileInfo info = mApi.getFile("/" + fileName, null, outputStream, null);
			Log.i("DbExampleLog", "The file's rev is: " + info.getMetadata().rev);
			
		} catch (FileNotFoundException e) {
			return false;
		} catch (DropboxException e) {
			return false;
		}
		
		return true;
	}
}
